import { Tabs } from 'nextra/components'
import { Callout, Sandpack } from '@/components'

# QueriesHydration

<Callout type='experimental'>

`<QueriesHydration/>` is an experimental feature, so this interface may change.

</Callout>

A component that prefetches multiple queries on the server and automatically hydrates them to client components.

This is useful when you want to prefetch data on the server side and pass it to the client in React Server Components environments.

> 💡 **Recommended reading**: Before using this component, we recommend reading the [TanStack Query Advanced Server Rendering guide](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr), which covers Server Components, streaming, and the Next.js App Router. Understanding these concepts will help you use `QueriesHydration` more effectively.

## Basic Usage

<Tabs items={['page.tsx (RSC)', 'UserProfile.tsx (RCC)', 'PostList.tsx (RCC)']}>
<Tabs.Tab>

```tsx /QueriesHydration/
import { QueriesHydration } from '@suspensive/react-query'
import { Suspense } from '@suspensive/react'
import { userQueryOptions, postsQueryOptions } from './queries'
import { UserProfile, PostList } from './_components'

// Server Component
const PostsPage = ({ userId }: { userId: number }) => {
  return (
    <>
      <Suspense fallback={<div>Loading user...</div>}>
        <QueriesHydration queries={[userQueryOptions(userId)]}>
          <UserProfile userId={userId} />
        </QueriesHydration>
      </Suspense>
      <Suspense fallback={<div>Loading posts...</div>}>
        <QueriesHydration queries={[postsQueryOptions(userId)]}>
          <PostList userId={userId} />
        </QueriesHydration>
      </Suspense>
    </>
  )
}
```

</Tabs.Tab>
<Tabs.Tab>

```tsx
'use client'

import { useSuspenseQuery } from '@suspensive/react-query'
import { userQueryOptions } from './queries'

export const UserProfile = ({ userId }: { userId: number }) => {
  // Data prefetched on the server is automatically hydrated
  const { data: user } = useSuspenseQuery(userQueryOptions(userId))

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}
```

</Tabs.Tab>
<Tabs.Tab>

```tsx
'use client'

import { useSuspenseQuery } from '@suspensive/react-query'
import { postsQueryOptions } from './queries'

export const PostList = ({ userId }: { userId: number }) => {
  // Data prefetched on the server is automatically hydrated
  const { data: posts } = useSuspenseQuery(postsQueryOptions(userId))

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
```

</Tabs.Tab>
</Tabs>

## Props

### queries

An array of queries to be prefetched on the server. Each query must have a `queryKey`.

```tsx
type QueriesHydrationProps = {
  queries: WithRequired<QueryOptions<any, any, any, any>, 'queryKey'>[]
  children: ReactNode
  queryClient?: QueryClient // Optional (default: new QueryClient())
  skipSsrOnError?: boolean | { fallback: ReactNode } // Optional (default: true)
}
```

### queryClient

Optionally, you can pass a QueryClient instance to use. If not provided, a new QueryClient instance will be created.

```tsx /QueriesHydration/
import { QueryClient } from '@tanstack/react-query'
import { QueriesHydration } from '@suspensive/react-query'

const queryClient = new QueryClient()

const Page = async () => {
  return (
    <QueriesHydration queryClient={queryClient} queries={[]}>
      {/* ... */}
    </QueriesHydration>
  )
}
```

### skipSsrOnError

Controls the behavior when an error occurs while fetching queries on the server. This option becomes clearer when you understand the 3 stages where data fetching occurs:

1. **RSC (React Server Component)**: Query execution in `QueriesHydration`
2. **RCC (React Client Component) - Server**: Query execution in `useSuspenseQuery` if no cached data exists
3. **RCC (React Client Component) - Browser**: Query execution in `useSuspenseQuery` if no cached or fresh data exists

If stage 1 fails, stage 2 is likely to fail for the same reason. However, stage 3 (browser) might succeed (e.g., authentication-related issues, etc.). Since the browser can retry and potentially succeed, it's better to use `skipSsrOnError` to skip RCC(server) and fetch directly from RCC(browser) when RSC fails.

**Important**: When stage 1 succeeds (no error), the data is already hydrated to the client. In this case, you can verify in the browser's Network tab that no additional fetch requests are made in stages 2 and 3, as the data is served from the cache. This demonstrates the efficiency of server-side prefetching with `QueriesHydration`.

- `true` (default): If stage 1 fails, skip SSR and retry in stage 3 (browser)
- `false`: Even if stage 1 fails, proceed to stage 2 without hydration (retry fetching on server)
- `{ fallback: ReactNode }`: If stage 1 fails, skip SSR and display a custom fallback UI while moving to stage 3

```tsx /QueriesHydration/
import { QueriesHydration } from '@suspensive/react-query'
import { Suspense } from '@suspensive/react'
import {
  userQueryOptions,
  postsQueryOptions,
  commentsQueryOptions,
} from './queries'
import { UserProfile, PostList, CommentList } from './_components'

const Page = async ({ userId }: { userId: number }) => {
  return (
    <>
      {/* Skip SSR and retry in browser if stage 1 fails (default behavior) */}
      <Suspense fallback={<div>Loading...</div>}>
        <QueriesHydration queries={[userQueryOptions(userId)]}>
          <UserProfile />
        </QueriesHydration>
      </Suspense>

      {/* Skip SSR and retry in browser with custom fallback if stage 1 fails */}
      <Suspense fallback={<div>Loading...</div>}>
        <QueriesHydration
          queries={[postsQueryOptions()]}
          skipSsrOnError={{
            fallback: <div>Unable to fetch data from server...</div>,
          }}
        >
          <PostList />
        </QueriesHydration>
      </Suspense>

      {/* Retry in stage 2 (RCC server) even if stage 1 fails */}
      <Suspense fallback={<div>Loading...</div>}>
        <QueriesHydration
          queries={[commentsQueryOptions()]}
          skipSsrOnError={false}
        >
          <CommentList />
        </QueriesHydration>
      </Suspense>
    </>
  )
}
```

## Motivation: Simplify prefetching multiple queries in Server Components

In React Server Components environments, you can prefetch data on the server to eliminate initial loading states. However, manually prefetching and dehydrating multiple queries is cumbersome.

### Traditional approach: Manual prefetch and dehydrate

<Tabs items={['page.tsx (RSC)', 'UserProfile.tsx (RCC)', 'PostList.tsx (RCC)']}>
<Tabs.Tab>

```tsx
import {
  QueryClient,
  dehydrate,
  HydrationBoundary,
} from '@tanstack/react-query'
import { Suspense } from '@suspensive/react'
import { userQueryOptions, postsQueryOptions } from './queries'
import { UserProfile, PostList } from './_components'

// Server Component
const PostsPage = ({ userId }: { userId: number }) => {
  return (
    <>
      <Suspense fallback={<div>Loading user...</div>}>
        <UserProfileWithData userId={userId} />
      </Suspense>
      <Suspense fallback={<div>Loading posts...</div>}>
        <PostListWithData userId={userId} />
      </Suspense>
    </>
  )
}

// Need to separate each server component for HTML Streaming
const UserProfileWithData = async ({ userId }: { userId: number }) => {
  const queryClient = new QueryClient()
  try {
    await queryClient.ensureQueryData(userQueryOptions(userId))
  } catch (error) {
    return (
      // If queryClient.ensureQueryData fails, use ClientOnly to prevent SSR and render on the browser directly
      <ClientOnly fallback={<div>Loading user...</div>}>
        <UserProfile userId={userId} />
      </ClientOnly>
    )
  }
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <UserProfile userId={userId} />
    </HydrationBoundary>
  )
}

const PostListWithData = async ({ userId }: { userId: number }) => {
  const queryClient = new QueryClient()
  try {
    await queryClient.ensureQueryData(postsQueryOptions(userId))
  } catch (error) {
    return (
      // If queryClient.ensureQueryData fails, use ClientOnly to prevent SSR and render on the browser directly
      <ClientOnly fallback={<div>Loading posts...</div>}>
        <PostList userId={userId} />
      </ClientOnly>
    )
  }
  return (
    <HydrationBoundary state={dehydrate(queryClient)}>
      <PostList userId={userId} />
    </HydrationBoundary>
  )
}
```

</Tabs.Tab>
<Tabs.Tab>

```tsx
'use client'

import { useSuspenseQuery } from '@suspensive/react-query'
import { userQueryOptions } from './queries'

export const UserProfile = ({ userId }: { userId: number }) => {
  const { data: user } = useSuspenseQuery(userQueryOptions(userId))

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}
```

</Tabs.Tab>
<Tabs.Tab>

```tsx
'use client'

import { useSuspenseQuery } from '@suspensive/react-query'
import { postsQueryOptions } from './queries'

export const PostList = ({ userId }: { userId: number }) => {
  const { data: posts } = useSuspenseQuery(postsQueryOptions(userId))

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
```

</Tabs.Tab>
</Tabs>

### Using QueriesHydration

With `<QueriesHydration/>`, all of this is handled automatically:

<Tabs items={['page.tsx (RSC)', 'UserProfile.tsx (RCC)', 'PostList.tsx (RCC)']}>
<Tabs.Tab>

```tsx /QueriesHydration/
import { QueriesHydration } from '@suspensive/react-query'
import { Suspense } from '@suspensive/react'
import { userQueryOptions, postsQueryOptions } from './queries'
import { UserProfile, PostList } from './_components'

// Server Component
const PostsPage = ({ userId }: { userId: number }) => {
  return (
    <>
      <Suspense fallback={<div>Loading user...</div>}>
        <QueriesHydration queries={[userQueryOptions(userId)]}>
          <UserProfile userId={userId} />
        </QueriesHydration>
      </Suspense>
      <Suspense fallback={<div>Loading posts...</div>}>
        <QueriesHydration queries={[postsQueryOptions(userId)]}>
          <PostList userId={userId} />
        </QueriesHydration>
      </Suspense>
    </>
  )
}
```

</Tabs.Tab>
<Tabs.Tab>

```tsx
'use client'

import { useSuspenseQuery } from '@suspensive/react-query'
import { userQueryOptions } from './queries'

export const UserProfile = ({ userId }: { userId: number }) => {
  // Data prefetched on the server is automatically hydrated
  const { data: user } = useSuspenseQuery(userQueryOptions(userId))

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}
```

</Tabs.Tab>
<Tabs.Tab>

```tsx
'use client'

import { useSuspenseQuery } from '@suspensive/react-query'
import { postsQueryOptions } from './queries'

export const PostList = ({ userId }: { userId: number }) => {
  // Data prefetched on the server is automatically hydrated
  const { data: posts } = useSuspenseQuery(postsQueryOptions(userId))

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
```

</Tabs.Tab>
</Tabs>

Key benefits:

1. **Concise code**: Automates QueryClient creation, prefetch, and dehydrate processes
2. **Parallel data fetching**: Uses `Promise.all` to process all queries in parallel
3. **Type safety**: Requires queryKey to prevent mistakes
4. **Consistent pattern**: Handles multiple queries consistently

## Dependent Queries and Streaming

When handling dependent queries, wrapping each component with a separate `QueriesHydration` maximizes streaming benefits by creating independent Suspense boundaries. By sharing the same `queryClient`, you can use the result of the first query in subsequent queries.

<Tabs items={['page.tsx (RSC)', 'ProductInfo.tsx (RCC)', 'ProductReviews.tsx (RCC)', 'RelatedProducts.tsx (RCC)']}>
<Tabs.Tab>

```tsx /QueriesHydration/
import { QueryClient } from '@tanstack/react-query'
import { QueriesHydration } from '@suspensive/react-query'
import { Suspense } from '@suspensive/react'
import {
  productQueryOptions,
  productReviewsQueryOptions,
  relatedProductsQueryOptions,
} from './queries'
import { ProductInfo, ProductReviews, RelatedProducts } from './_components'

const ProductPage = async ({ productId }: { productId: string }) => {
  const queryClient = new QueryClient()

  // 1. First, fetch product information
  const product = await queryClient.ensureQueryData(
    productQueryOptions(productId)
  )

  return (
    <>
      {/* Product information */}
      <Suspense fallback={<div>Loading product...</div>}>
        <QueriesHydration
          queryClient={queryClient}
          queries={[productQueryOptions(productId)]}
        >
          <ProductInfo productId={productId} />
        </QueriesHydration>
      </Suspense>

      {/* Product reviews: depends on product information (e.g., filtered by product category) */}
      <Suspense fallback={<div>Loading reviews...</div>}>
        <QueriesHydration
          queryClient={queryClient}
          queries={[productReviewsQueryOptions(productId)]}
        >
          <ProductReviews productId={productId} />
        </QueriesHydration>
      </Suspense>

      {/* Related products: depends on product information (same category) */}
      <Suspense fallback={<div>Loading related products...</div>}>
        <QueriesHydration
          queryClient={queryClient}
          queries={[relatedProductsQueryOptions(product.categoryId)]}
        >
          <RelatedProducts categoryId={product.categoryId} />
        </QueriesHydration>
      </Suspense>
    </>
  )
}
```

</Tabs.Tab>
<Tabs.Tab>

```tsx
'use client'

import { useSuspenseQuery } from '@suspensive/react-query'
import { productQueryOptions } from './queries'

export const ProductInfo = ({ productId }: { productId: string }) => {
  // Data prefetched on the server is automatically hydrated
  const { data: product } = useSuspenseQuery(productQueryOptions(productId))

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>Price: {product.price}</p>
    </div>
  )
}
```

</Tabs.Tab>
<Tabs.Tab>

```tsx
'use client'

import { useSuspenseQuery } from '@suspensive/react-query'
import { productReviewsQueryOptions } from './queries'

export const ProductReviews = ({ productId }: { productId: string }) => {
  // Data prefetched on the server is automatically hydrated
  const { data: reviews } = useSuspenseQuery(
    productReviewsQueryOptions(productId)
  )

  return (
    <div>
      <h2>Reviews</h2>
      <ul>
        {reviews.map((review) => (
          <li key={review.id}>
            <p>{review.comment}</p>
            <p>Rating: {review.rating}</p>
          </li>
        ))}
      </ul>
    </div>
  )
}
```

</Tabs.Tab>
<Tabs.Tab>

```tsx
'use client'

import { useSuspenseQuery } from '@suspensive/react-query'
import { relatedProductsQueryOptions } from './queries'

export const RelatedProducts = ({ categoryId }: { categoryId: string }) => {
  // Data prefetched on the server is automatically hydrated
  const { data: relatedProducts } = useSuspenseQuery(
    relatedProductsQueryOptions(categoryId)
  )

  return (
    <div>
      <h2>Related Products</h2>
      <ul>
        {relatedProducts.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  )
}
```

</Tabs.Tab>
</Tabs>

Benefits of this pattern:

1. **Independent Suspense boundaries**: Each component streams independently, so `ProductInfo` renders first when ready
2. **Shared queryClient**: Cache is shared, allowing reuse of `product` data
3. **Dependent queries support**: You can pass `product.categoryId` to `relatedProductsQueryOptions`
4. **Progressive rendering**: Components stream sequentially as they become ready

## Example

Here's a live example demonstrating `QueriesHydration` with Next.js streaming:

![Next.js Streaming React Query Example](/img/next-streaming-react-query-example.gif)

- **[Live Demo](https://next-streaming-react-query.suspensive.org)**: See it in action
- **[Source Code](https://github.com/toss/suspensive/tree/main/examples/next-streaming-react-query)**: View the full implementation

**Tip**: In the live demo, check the "4. no error (Best Practice)" case and open the browser's Developer Tools Network tab. You'll notice that no fetch requests are made because the data was successfully prefetched on the server and hydrated to the client, demonstrating the efficiency of `QueriesHydration`.

## Disabling SSR

If you want to skip server-side rendering for a specific component, simply add the `clientOnly` prop to `<Suspense/>`. In this case, since you don't need to prefetch data on the server, you don't need `QueriesHydration` either:

```tsx /QueriesHydration/
import { QueriesHydration } from '@suspensive/react-query'
import { Suspense } from '@suspensive/react'
import { userQueryOptions, postsQueryOptions } from './queries'
import { UserProfile, PostList } from './_components'

const PostsPage = ({ userId }: { userId: number }) => {
  return (
    <>
      {/* UserProfile skips SSR and only renders on the client */}
      <Suspense clientOnly fallback={<div>Loading user...</div>}>
        <UserProfile userId={userId} />
      </Suspense>
      {/* PostList is prefetched and rendered on the server */}
      <Suspense fallback={<div>Loading posts...</div>}>
        <QueriesHydration queries={[postsQueryOptions(userId)]}>
          <PostList userId={userId} />
        </QueriesHydration>
      </Suspense>
    </>
  )
}
```

When using the `clientOnly` prop:

- Components within that Suspense boundary will not be rendered on the server
- Data will only be fetched and rendered on the client
- You don't need `QueriesHydration` since server-side prefetch is not needed

## Version Differences

### @tanstack/react-query v5

In `@tanstack/react-query` v5, it uses the `HydrationBoundary` component.

```tsx
import { HydrationBoundary } from '@tanstack/react-query'
import { QueriesHydration } from '@suspensive/react-query'

// QueriesHydration internally uses HydrationBoundary
```

### @tanstack/react-query v4

In `@tanstack/react-query` v4, it uses the `Hydrate` component.

```tsx
import { Hydrate } from '@tanstack/react-query'
import { QueriesHydration } from '@suspensive/react-query'

// QueriesHydration internally uses Hydrate
```

## Important Notes

- This component is an **async server component**.
- It can only be used in frameworks that support React Server Components (Next.js 13+ App Router, etc.).
- All queries must include a `queryKey`.

### Version History

| Version | Changes                                                          |
| ------- | ---------------------------------------------------------------- |
| v3.14.0 | `<QueriesHydration/>` has been added as an experimental feature. |
